class BoardError < StandardError; end

class Board < ActiveRecord::Base
  has_many :moves
  belongs_to :game
  
=begin 

 Q1 | Q0
----|----
 Q2 | Q3

=end  
  def initialize_board    
    @board = [
      [
        [:d, 1, nil], [:e, 1, nil], [:f, 1, nil],
        [:d, 2, nil], [:e, 2, nil], [:f, 2, nil],
        [:d, 3, nil], [:e, 3, nil], [:f, 3, nil]
      ],
      [
        [:a, 1, nil], [:b, 1, nil], [:c, 1, nil], 
        [:a, 2, nil], [:b, 2, nil], [:c, 2, nil], 
        [:a, 3, nil], [:b, 3, nil], [:c, 3, nil]
      ],
      [
        [:a, 4, nil], [:b, 4, nil], [:c, 4, nil],
        [:a, 5, nil], [:b, 5, nil], [:c, 5, nil], 
        [:a, 6, nil], [:b, 6, nil], [:c, 6, nil]
      ],
      [
        [:d, 4, nil], [:e, 4, nil], [:f, 4, nil],
        [:d, 5, nil], [:e, 5, nil], [:f, 5, nil],
        [:d, 6, nil], [:e, 6, nil], [:f, 6, nil]
      ]
    ]
  end
  
  def rotate_quadrant(quadrant, direction)
    case direction
    when 1
      @board[quadrant] = [
        [@board[quadrant][0][0], @board[quadrant][0][1], @board[quadrant][6][2]],
        [@board[quadrant][1][0], @board[quadrant][1][1], @board[quadrant][3][2]],
        [@board[quadrant][2][0], @board[quadrant][2][1], @board[quadrant][0][2]],
        [@board[quadrant][3][0], @board[quadrant][3][1], @board[quadrant][7][2]],
        @board[quadrant][4],
        [@board[quadrant][5][0], @board[quadrant][5][1], @board[quadrant][1][2]],
        [@board[quadrant][6][0], @board[quadrant][6][1], @board[quadrant][8][2]],
        [@board[quadrant][7][0], @board[quadrant][7][1], @board[quadrant][5][2]],
        [@board[quadrant][8][0], @board[quadrant][8][1], @board[quadrant][2][2]]
      ]
    when -1
      @board[quadrant] = [
        [@board[quadrant][0][0], @board[quadrant][0][1], @board[quadrant][2][2]],
        [@board[quadrant][1][0], @board[quadrant][1][1], @board[quadrant][5][2]],
        [@board[quadrant][2][0], @board[quadrant][2][1], @board[quadrant][8][2]],
        [@board[quadrant][3][0], @board[quadrant][3][1], @board[quadrant][1][2]],
        @board[quadrant][4],
        [@board[quadrant][5][0], @board[quadrant][5][1], @board[quadrant][7][2]],
        [@board[quadrant][6][0], @board[quadrant][6][1], @board[quadrant][0][2]],
        [@board[quadrant][7][0], @board[quadrant][7][1], @board[quadrant][3][2]],
        [@board[quadrant][8][0], @board[quadrant][8][1], @board[quadrant][6][2]]
      ]
    else
      raise BoardError, "Invalid direction, must be 1 (clockwise) or -1 (counter-clockwise)"
    end
  end
  
  def flat_board
    @board[0] + @board[1] + @board[2] + @board[3]
  end
  
  def mark(location, player=current_player)
    x_loc, y_loc = location.split(//)
    
    unless board_location = flat_board.detect {|x, y, marking| x.to_s == x_loc && y.to_s == y_loc}
      raise BoardError, "Invalid board location (#{location})"
    end
    
    if board_location[2]
      raise BoardError, "Position already marked"
    end
    
    board_location_index = flat_board.index(board_location)
    quadrant = ((board_location_index / 36.0) * 4).floor
    
    @board[quadrant][board_location_index % 9] = [x_loc.intern, y_loc.to_i, player]
  end
  
  def update_with_moves(last_move=:latest)
    if @last_move == last_move.to_i
      true
    else
      initialize_board

      @last_move = last_move
      
      case last_move
      when :latest
        moves
      else
        moves[0..last_move.to_i]
      end.each_with_index do |move, i|
        mark(move.location, (i % 2) + 1)
        return true if won?(false)
        rotate_quadrant(move.quadrant, move.direction)
      end
      
      true
    end
  end
  
  def active_areas
    flat_board.select{|x,y,m| !m.nil?}
  end
  
  def to_a
    @board || initialize_board
  end
  
  def current_player
    (moves.size % 2) + 1
  end
  
  def current_player_token
    if moves.size % 2 == 0
      game.player1_token
    else
      game.player2_token
    end
  end
  
  def complete?
    won?
  end
  
  def won?(update=true)
    player1_won?(update) || player2_won?(update)
  end
  
  def player1_won?(update=true)
    update_with_moves if update
    
    # vertical
    ('a'..'f').each do |x_loc|
      return true if flat_board.select {|x, y, marking| x.to_s == x_loc && (2..5).include?(y)}.all? {|x, y, marking| marking == 1} &&
        (flat_board.include?([x_loc.intern, 1, 1]) || flat_board.include?([x_loc.intern, 6, 1]))
    end

    # horizontal
    (1..6).each do |y_loc|
      return true if flat_board.select {|x, y, marking| y == y_loc && ('b'..'e').include?(x.to_s)}.all? {|x, y, marking| marking == 1} &&
        (flat_board.include?([:a, y_loc, 1]) || flat_board.include?([:f, y_loc, 1]))
    end
    
    # diagonal middle
    return true if flat_board.include?([:e, 5, 1]) &&
      flat_board.include?([:a, 1, 1]) &&
      flat_board.include?([:b, 2, 1]) &&
      flat_board.include?([:c, 3, 1]) &&
      flat_board.include?([:d, 4, 1])

    return true if flat_board.include?([:f, 6, 1]) &&
      flat_board.include?([:b, 2, 1]) &&
      flat_board.include?([:c, 3, 1]) &&
      flat_board.include?([:d, 4, 1]) &&
      flat_board.include?([:e, 5, 1])
    
    return true if flat_board.include?([:e, 2, 1]) &&
      flat_board.include?([:a, 6, 1]) &&
      flat_board.include?([:b, 5, 1]) &&
      flat_board.include?([:c, 4, 1]) &&
      flat_board.include?([:d, 3, 1])

    return true if flat_board.include?([:f, 1, 1]) &&
      flat_board.include?([:b, 5, 1]) &&
      flat_board.include?([:c, 4, 1]) &&
      flat_board.include?([:d, 3, 1]) &&
      flat_board.include?([:e, 2, 1])
    
    # diagonal side
    return true if flat_board.include?([:e, 6, 1]) &&
      flat_board.include?([:a, 2, 1]) &&
      flat_board.include?([:b, 3, 1]) &&
      flat_board.include?([:c, 4, 1]) &&
      flat_board.include?([:d, 5, 1])

    return true if flat_board.include?([:f, 5, 1]) &&
      flat_board.include?([:b, 1, 1]) &&
      flat_board.include?([:c, 2, 1]) &&
      flat_board.include?([:d, 3, 1]) &&
      flat_board.include?([:e, 4, 1])
    
    return true if flat_board.include?([:e, 1, 1]) &&
      flat_board.include?([:a, 5, 1]) &&
      flat_board.include?([:b, 4, 1]) &&
      flat_board.include?([:c, 3, 1]) &&
      flat_board.include?([:d, 2, 1])

    return true if flat_board.include?([:f, 2, 1]) &&
      flat_board.include?([:b, 6, 1]) &&
      flat_board.include?([:c, 5, 1]) &&
      flat_board.include?([:d, 4, 1]) &&
      flat_board.include?([:e, 3, 1])
    
    return false
  end
  
  def player2_won?(update=true)
    update_with_moves if update
    
    # vertical
    ('a'..'f').each do |x_loc|
      return true if flat_board.select {|x, y, marking| x.to_s == x_loc && (2..5).include?(y)}.all? {|x, y, marking| marking == 2} &&
        (flat_board.include?([x_loc.intern, 1, 2]) || flat_board.include?([x_loc.intern, 6, 2]))
    end

    # horizontal
    (1..6).each do |y_loc|
      return true if flat_board.select {|x, y, marking| y == y_loc && ('b'..'e').include?(x.to_s)}.all? {|x, y, marking| marking == 2} &&
        (flat_board.include?([:a, y_loc, 2]) || flat_board.include?([:f, y_loc, 2]))
    end
    
    # diagonal middle
    return true if flat_board.include?([:e, 5, 2]) &&
      flat_board.include?([:a, 1, 2]) &&
      flat_board.include?([:b, 2, 2]) &&
      flat_board.include?([:c, 3, 2]) &&
      flat_board.include?([:d, 4, 2])

    return true if flat_board.include?([:f, 6, 2]) &&
      flat_board.include?([:b, 2, 2]) &&
      flat_board.include?([:c, 3, 2]) &&
      flat_board.include?([:d, 4, 2]) &&
      flat_board.include?([:e, 5, 2])
    
    return true if flat_board.include?([:e, 2, 2]) &&
      flat_board.include?([:a, 6, 2]) &&
      flat_board.include?([:b, 5, 2]) &&
      flat_board.include?([:c, 4, 2]) &&
      flat_board.include?([:d, 3, 2])

    return true if flat_board.include?([:f, 1, 2]) &&
      flat_board.include?([:b, 5, 2]) &&
      flat_board.include?([:c, 4, 2]) &&
      flat_board.include?([:d, 3, 2]) &&
      flat_board.include?([:e, 2, 2])
    
    # diagonal side
    return true if flat_board.include?([:e, 6, 2]) &&
      flat_board.include?([:a, 2, 2]) &&
      flat_board.include?([:b, 3, 2]) &&
      flat_board.include?([:c, 4, 2]) &&
      flat_board.include?([:d, 5, 2])

    return true if flat_board.include?([:f, 5, 2]) &&
      flat_board.include?([:b, 1, 2]) &&
      flat_board.include?([:c, 2, 2]) &&
      flat_board.include?([:d, 3, 2]) &&
      flat_board.include?([:e, 4, 2])
    
    return true if flat_board.include?([:e, 1, 2]) &&
      flat_board.include?([:a, 5, 2]) &&
      flat_board.include?([:b, 4, 2]) &&
      flat_board.include?([:c, 3, 2]) &&
      flat_board.include?([:d, 2, 2])

    return true if flat_board.include?([:f, 2, 2]) &&
      flat_board.include?([:b, 6, 2]) &&
      flat_board.include?([:c, 5, 2]) &&
      flat_board.include?([:d, 4, 2]) &&
      flat_board.include?([:e, 3, 2])
    
    return false
  end
  
  def tied?(update=true)
    player1_won?(update) and player2_won?(update)
  end
end
